<?php

/***
 * php 设计模式设配器 
 * 解决相同功能或者相同业务的两个公司出现架构不同要进行适配的问题。如果过a公司要介入b公司就要a对b进行适配，不然则反之。
 * 什么时候会用到适配器模式 
 * 
 * 其实最简单的例子是当我们引用一个第三方类库。这个类库随着版本的改变，它提供的API也可能会改变。
 * 如果很不幸的是，你的应用里引用的某个API已经发生改变的时候，除了在心中默默地骂“wocao”之外，
 * 你还得去硬着头皮去改大量的代码
 * 例子：黑枣玩具公司专门生产玩具，生产的玩具不限于狗、猫、狮子，鱼等动物。每个玩具都可以进行“张嘴”
 * 与“闭嘴”操作，分别调用了openMouth与closeMouth方法。
 *　在这个时候，我们很容易想到可以第一定义一个抽象类Toy,甚至是接口Toy,这些问题不大，其他的类去继
 * 承父类，实现父类的方法。一片和谐，信心向荣。
 */
// abstract class Toy
// {

// public abstract function openMouth();

// public abstract function closeMouth();
// }

// class Dog extends Toy
// {

// public function openMouth()
// {
// echo "Dog open Mouth\n";
// }

// public function closeMouth()
// {
// echo "Dog open Mouth\n";
// }
// }

// class Cat extends Toy
// {

// public function openMouth()
// {
// echo "Cat open Mouth\n";
// }

// public function closeMouth()
// {
// echo "Cat open Mouth\n";
// }
// }
/**
 * 为了扩大业务，现在黑枣玩具公司与红枣遥控公司合作，红枣遥控公司可以使用遥控设备对动物进行嘴巴控制。
 * 不过红枣遥控公司的遥控设备是调用的动物的doMouthOpen及doMouthClose方法。黑枣玩具公司的
 * 程序员现在必须要做的是对Toy系列类进行升级改造，使Toy能调用doMouthOpen及doMouthClose方法。
 *
 * 考虑实现的方法时，我们很直接地想到，你需要的话我再在我的父类子类里给你添加这么两个方法就好啦。
 * 当你一次又一次在父类子类里面重复添加着这两个方法的时候，总会想着如此重复的工作，难道不能解决么？当
 * 有数百个子类的时候，程序员会改疯的。程序员往往比的是谁在不影响效率的时候更会“偷懒”。这样做下去程序员
 * 会觉得自己很傻。(其实我经常当这样的傻子)
 */

// abstract class Toy
// {
// public abstract function openMouth();

// public abstract function closeMouth();

// //为红枣遥控公司控制接口增加doMouthOpen方法
// public abstract function doMouthOpen();

// //为红枣遥控公司控制接口增加doMouthClose方法
// public abstract function doMouthClose();
// }

// class Dog extends Toy
// {
// public function openMouth()
// {
// echo "Dog open Mouth\n";
// }

// public function closeMouth()
// {
// echo "Dog open Mouth\n";
// }

// //增加的方法
// public function doMouthOpen()
// {
// $this->doMouthOpen();
// }

// //增加的方法
// public function doMouthClose()
// {
// $this->closeMouth();
// }
// }

// class Cat extends Toy
// {
// public function openMouth()
// {
// echo "Cat open Mouth\n";
// }

// public function closeMouth()
// {
// echo "Cat open Mouth\n";
// }

// //增加的方法
// public function doMouthOpen()
// {
// $this->doMouthOpen();
// }

// //增加的方法
// public function doMouthClose()
// {
// $this->closeMouth();
// }
// }

/**
 * *
 *
 * 程序员刚刚码完代码，喝了口水，突然间另一个消息传来。
 *
 * 黑枣玩具公司也要与绿枣遥控公司合作，因为绿枣遥控公司遥控设备更便宜稳定。不过绿枣遥控公司的遥
 * 控设备是调用的动物的operMouth(type)方法来实现嘴巴控制。如果type)方法来实现嘴巴控制。
 * 如果type为0则“闭嘴”，反之张嘴。
 *
 * 这下好了，程序员又得对Toy及其子类进行升级，使Toy能调用operMouth()方法。搁谁都不淡定了。
 */

// abstract class Toy
// {
// public abstract function openMouth();

// public abstract function closeMouth();

// public abstract function doMouthOpen();

// public abstract function doMouthClose();

// //为绿枣遥控公司控制接口增加doMouthClose方法
// public abstract function operateMouth($type = 0);
// }

// class Dog extends Toy
// {
// public function openMouth()
// {
// echo "Dog open Mouth\n";
// }

// public function closeMouth()
// {
// echo "Dog open Mouth\n";
// }

// public function doMouthOpen()
// {
// $this->doMouthOpen();
// }

// public function doMouthClose()
// {
// $this->closeMouth();
// }

// public function operateMouth($type = 0)
// {
// if ($type == 0) {
// $this->closeMouth();
// } else {
// $this->operateMouth();
// }
// }
// }

// class Cat extends Toy
// {
// public function openMouth()
// {
// echo "Cat open Mouth\n";
// }

// public function closeMouth()
// {
// echo "Cat open Mouth\n";
// }

// public function doMouthOpen()
// {
// $this->doMouthOpen();
// }

// public function doMouthClose()
// {
// $this->closeMouth();
// }

// public function operateMouth($type = 0)
// {
// if ($type == 0) {
// $this->closeMouth();
// } else {
// $this->operateMouth();
// }
// }
// }

/**
 * *
 *
 * 在这个时候，程序员必须要动脑子想办法了，就算自己勤快，万一哪天紫枣青枣黄枣山枣这些遥控公司全来的时候，
 * 忽略自己不断增多的工作量不说，这个Toy类可是越来越大，总有一天程序员不崩溃，系统也会崩溃。
 *
 * 问题在出在哪里呢？
 *
 * 像上面那样编写代码，代码实现违反了“开-闭”原则，一个软件实体应当对扩展开放，对修改关闭。即在设计一个
 * 模块的时候，应当使这个模块可以在不被修改的前提下被扩展。也就是说每个尸体都是一个小王国，你让我参与你的事
 * 情这个可以，但你不能修改我的内部，除非我的内部代码确实可以优化。
 *
 * 在这种想法下，我们懂得了如何去用继承，如何利用多态，甚至如何实现“高内聚，低耦合”。
 *
 * 回到这个问题，我们现在面临这么一个问题，新的接口方法我要实现，旧的接口（Toy抽象类）也不能动，那么总
 * 得有个解决方法吧。那就是引入一个新的类--我们本文的主角--适配器。 适配器要完成的功能很明确，引用现有接口
 * 的方法实现新的接口的方法。更像它名字描述的那样，你的接口不改的话，我就利用现有接口和你对接一下吧。
 *
 * 到此，解决方法已经呼之欲出了，下面贴上代码。
 */
abstract class Toy
{

    public abstract function openMouth();

    public abstract function closeMouth();
}

class Dog extends Toy
{

    public function openMouth()
    {
        echo "Dog open Mouth\n";
    }

    public function closeMouth()
    {
        echo "Dog close Mouth\n";
    }
}

class Cat extends Toy
{

    public function openMouth()
    {
        echo "Cat open Mouth\n";
    }

    public function closeMouth()
    {
        echo "Cat close Mouth\n";
    }
}

// 目标角色:红枣遥控公司
interface RedTarget
{

    public function doMouthOpen();

    public function doMouthClose();
}

// 目标角色:绿枣遥控公司及
interface GreenTarget
{

    public function operateMouth($type = 0);
}

// 类适配器角色:红枣遥控公司
class RedAdapter implements RedTarget
{

    private $adaptee;

    function __construct(Toy $adaptee)
    {
        $this->adaptee = $adaptee;
    }

    // 委派调用Adaptee的sampleMethod1方法
    public function doMouthOpen()
    {
        $this->adaptee->openMouth();
    }

    public function doMouthClose()
    {
        $this->adaptee->closeMouth();
    }
}

// 类适配器角色:绿枣遥控公司
class GreenAdapter implements GreenTarget
{

    private $adaptee;

    function __construct(Toy $adaptee)
    {
        $this->adaptee = $adaptee;
    }

    // 委派调用Adaptee：GreenTarget的operateMouth方法
    public function operateMouth($type = 0)
    {
        if ($type) {
            $this->adaptee->openMouth();
        } else {
            $this->adaptee->closeMouth();
        }
    }
}

class testDriver
{

    public function run()
    {
        // 实例化一只狗玩具
        $adaptee_dog = new Dog();
        echo "给狗套上红枣适配器\n";
        $adapter_red = new RedAdapter($adaptee_dog);
        // 张嘴
        $adapter_red->doMouthOpen();
        // 闭嘴
        $adapter_red->doMouthClose();
        echo "给狗套上绿枣适配器\n";
        $adapter_green = new GreenAdapter($adaptee_dog);
        // 张嘴
        $adapter_green->operateMouth(1);
        // 闭嘴
        $adapter_green->operateMouth(0);
    }
}

$test = new testDriver();
$test->run();

/**
 * *
 * 例子 2
 * 
 * @author jiliangliang
 *        
 */
interface BookInterface
{

    public function turnPage();

    public function open();

    public function getPage(): int;
}

class Book implements BookInterface
{

    /**
     *
     * @var int
     */
    private $page;

    public function open()
    {
        $this->page = 1;
    }

    public function turnPage()
    {
        $this->page ++;
    }

    public function getPage(): int
    {
        return $this->page;
    }
}

/**
 * 这里是一个适配器.
 * 注意他实现了 BookInterface,
 * 因此你不必去更改客户端代码当使用 Book
 */
class EBookAdapter implements BookInterface
{

    /**
     *
     * @var EBookInterface
     */
    protected $eBook;

    /**
     *
     * @param EBookInterface $eBook            
     */
    public function __construct(EBookInterface $eBook)
    {
        $this->eBook = $eBook;
    }

    /**
     * 这个类使接口进行适当的转换.
     */
    public function open()
    {
        $this->eBook->unlock();
    }

    public function turnPage()
    {
        $this->eBook->pressNext();
    }

    /**
     * 注意这里适配器的行为： EBookInterface::getPage() 将返回两个整型，除了 BookInterface
     * 仅支持获得当前页，所以我们这里适配这个行为
     *
     * @return int
     */
    public function getPage(): int
    {
        return $this->eBook->getPage()[0];
    }
}

interface EBookInterface
{

    public function unlock();

    public function pressNext();

    /**
     * 返回当前页和总页数，像 [10, 100] 是总页数100中的第10页。
     *
     * @return int[]
     */
    public function getPage(): array;
}

/**
 * 这里是适配过的类.
 * 在生产代码中, 这可能是来自另一个包的类，一些供应商提供的代码。
 * 注意它使用了另一种命名方案并用另一种方式实现了类似的操作
 */
class Kindle implements EBookInterface
{

    /**
     *
     * @var int
     */
    private $page = 1;

    /**
     *
     * @var int
     */
    private $totalPages = 100;

    public function pressNext()
    {
        $this->page ++;
    }

    public function unlock()
    {}

    /**
     * 返回当前页和总页数，像 [10, 100] 是总页数100中的第10页。
     *
     * @return int[]
     */
    public function getPage(): array
    {
        return [
            $this->page,
            $this->totalPages
        ];
    }
}

$book = new Book();
$book->open();
$Kindle = new Kindle();
$ebookAdapter = new EBookAdapter($Kindle);
$ebookAdapter->open();